// Copyright 2012 Google Inc. All Rights Reserved.

package com.google.appengine.syncserver.processor;

import com.google.appengine.syncserver.entities.PreferencesValue;
import com.google.appengine.syncserver.entities.RequestPageCache;
import com.google.appengine.syncserver.entities.SharedPreferences;
import com.google.appengine.syncserver.shared.PropertyChange;
import com.google.appengine.syncserver.shared.WriteUpdatesPageRequest;
import com.google.appengine.syncserver.shared.WriteUpdatesResponse;

import java.util.Map.Entry;
import java.util.Set;

import javax.servlet.http.HttpServletResponse;

/**
 * Process changes sent from the client to Server.
 */
public class WriteProcessor extends AbstractProcessor {

  private static final String EMPTY_RESPONSE = new WriteUpdatesResponse(-1).toString();

  /**
   * Copy entries of cache to shared preferences.
   *
   * @param cache
   * @param sharedPreferences
   */
  private void writeCacheToSharedPrefs(
      RequestPageCache cache, SharedPreferences sharedPreferences) {
    Set<Entry<String, PreferencesValue>> entrySet = cache.getAllProperties().entrySet();
    // TODO(chintanraoh): Add get all to cache.
    for (Entry<String, PreferencesValue> entry : entrySet) {
      if (entry.getValue() == null) {
        sharedPreferences.removeProperty(entry.getKey());
      } else {
        sharedPreferences.setProperty(entry.getKey(), entry.getValue());
      }
    }
  }

  private RequestPageCache writePageToCache(WriteUpdatesPageRequest writePage) throws Exception {

    // Get the old cache object.
    RequestPageCache cache = new RequestPageCache(writePage.getUsername(), writePage
        .getPreferencesName(), writePage.getClientId());

    // TODO(chintanraoh): Check if cache is not empty.
    // If this the first page written, clear() old cache.
    if (writePage.isFirstPage()) {
      cache.clearPage();
      cache = new RequestPageCache(writePage.getUsername(), writePage.getPreferencesName(),
          writePage.getClientId());
    }

    // Write the page to cache.
    writeToCache(cache, writePage);

    return cache;
  }

  /**
   * Write the client pages to cache. But does not commit changes to Datastore.
   *
   * @param cache
   * @param writePage
   */
  private void writeToCache(RequestPageCache cache, WriteUpdatesPageRequest writePage) {
    // Get changes from the client.
    PropertyChange[] changes = writePage.getPropertyChanges();

    // Writes the changes to
    for (PropertyChange change : changes) {
      // TODO(chintanraoh): Refractor updateType to change type.
      switch (change.getType()) {
        case UPDATE:
          cache.setProperty(change.getKey(), new PreferencesValue(change.getValue()));
          break;
        case DELETE:
          cache.setProperty(change.getKey(), null);
          break;
      }
    }
  }

  /**
   * Write updates in cache (client changes) to shared preferences.
   *
   * @param lastPage the last client updates page
   * @return {@link WriteUpdatesResponse} object
   * @throws Exception
   */
  private WriteUpdatesResponse writeToSharedPrefs(
      WriteUpdatesPageRequest lastPage, RequestPageCache cache) throws Exception {

    SharedPreferences sharedPreferences = null;

    try {
      sharedPreferences = new SharedPreferences(
          lastPage.getUsername(), lastPage.getPreferencesName());

      // We need to check whether server state has changed since the client read.
      // We do this by comparing max versions of the client and server.
      if (sharedPreferences.getMaxVersion() != lastPage.getMaxVersion()) {
        throw new HttpException(HttpServletResponse.SC_CONFLICT);
      }

      // We have cached the client update pages. Put updates from the cache to the
      // shared preferences.
      writeCacheToSharedPrefs(cache, sharedPreferences);
      sharedPreferences.commit();

      return new WriteUpdatesResponse(sharedPreferences.getMaxVersion());
    } finally {
      if (sharedPreferences != null) {
        sharedPreferences.rollBackIfActive();
      }
    }
  }

  @Override
  public String processRequest(String requestString)
      throws Exception {
    WriteUpdatesPageRequest request = WriteUpdatesPageRequest.valueOf(requestString);

    // Cache request/changes from the client.
    RequestPageCache cache = writePageToCache(request);
    if (!request.isLastPage()) {
      // write cache to datastore.
      cache.commit();
      // Intermediate pages don't have any useful response.
      return EMPTY_RESPONSE;
    }

    // Else we don't call commit. That is we don't write the additional page to
    // datastore. We just copy cache to datastore.
    return writeToSharedPrefs(request, cache).toString();
  }
}
